namespace TfsExtensions.Build.Activities
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Xml;
    using TfsExtensions.Build;
    using TfsExtensions.Build.Activities.Entities;
    using System.Activities;
    using Microsoft.TeamFoundation.Build.Workflow.Tracking;
    using Microsoft.TeamFoundation.Build.Workflow.Activities;
    using Microsoft.TeamFoundation.Build.Client;

    public class UpdateProject : CodeActivity
    {
        private const string KeyFileNode = "AssemblyOriginatorKeyFile";
        private const string ProjectPropertyGroupNode = "PropertyGroup";
        private const string KeyContainerNode = "AssemblyKeyContainerName";
        private const string KeyMode = "AssemblyOriginatorKeyMode";
        private const string SignAssemblyNode = "SignAssembly";
        private const string DelaySignNode = "DelaySign";

        public InArgument<IEnumerable<String>> ProjectFilesToProcess { get; set; }
        public InArgument<AssemblySettings> AssemblyMetadataSettings { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var result = this.ExecuteInternal(context);
        }

        public bool ExecuteInternal(CodeActivityContext context)
        {
            try
            {
                bool result = true;
                XmlDocument projDoc;

                foreach (string projectPathOnDisk in ProjectFilesToProcess.Get(context))
                {
                        try
                        {
                            LogHelper.LogMessage(context, BuildMessageImportance.Normal, "Located project file {0}", new object[] { projectPathOnDisk });
                            if (File.Exists(projectPathOnDisk))
                            {
                                this.ChangeReadOnlyStatus(projectPathOnDisk);
                                projDoc = new XmlDocument();
                                projDoc.Load(projectPathOnDisk);
                                
                                XmlNodeList projectPropertyGroupNodes = projDoc.DocumentElement.GetElementsByTagName(ProjectPropertyGroupNode);
                                foreach (XmlNode propNode in projectPropertyGroupNodes)
                                {
                                    if (this.AssemblyMetadataSettings.Get(context).AssemblySigningDetails.IsAssemblySigning)
                                    {
                                        this.UpdateNode(context, projDoc, propNode, true, SignAssemblyNode, bool.TrueString.ToLowerInvariant());

                                        var signKeyContent = AssemblyMetadataSettings.Get(context).AssemblySigningDetails.SignKeyContent;
                                        var signKeyFilePath = AssemblyMetadataSettings.Get(context).AssemblySigningDetails.StrongKeyFilePath;
                                        if (signKeyContent != null && signKeyContent.Length > 0)
                                        {
                                            var keyfile = signKeyFilePath;
                                            keyfile = keyfile.Substring(keyfile.LastIndexOf(@"\") + 1);
                                            string filePath = Path.Combine(Path.GetTempPath(), keyfile);

                                            using (var mstream = new MemoryStream(signKeyContent))
                                            {
                                                using (var fstream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                                                {
                                                    mstream.WriteTo(fstream);
                                                    fstream.Flush();
                                                    mstream.Flush();
                                                }
                                            }

                                            this.UpdateNode(context, projDoc, propNode, true, KeyFileNode, filePath);
                                            LogHelper.LogMessage(context, "Updated " + projectPathOnDisk + " with sign key " + filePath);
                                        }

                                        if (!string.IsNullOrEmpty(AssemblyMetadataSettings.Get(context).AssemblySigningDetails.StrongKeyName))
                                        {
                                            this.UpdateNode(context, projDoc, propNode, true, KeyContainerNode, AssemblyMetadataSettings.Get(context).AssemblySigningDetails.StrongKeyName);
                                        }

                                        if (AssemblyMetadataSettings.Get(context).AssemblySigningDetails.IsDelaySigned)
                                        {
                                            this.UpdateNode(context, projDoc, propNode, true, DelaySignNode, bool.TrueString.ToLowerInvariant());
                                        }
                                        else
                                        {
                                            this.UpdateNode(context, projDoc, propNode, true, DelaySignNode, bool.FalseString.ToLowerInvariant());
                                        }
                                    }
                                }
                                projDoc.Save(projectPathOnDisk);
                                projDoc = null;
                            }
                        }
                        catch (BuildTasksException ex)
                        {
                            LogHelper.LogError(context, "Project: " + projectPathOnDisk + "; Exception: " + ex.Message);
                            result = false;
                        }
                    
                }
                return result;
            }
            catch (Exception ex)
            {
                LogHelper.LogError(context, "Error processing request: " + ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Updates the node.
        /// </summary>
        /// <param name="doc">The document.</param>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="enableSigning">if set to <c>true</c> [enable signing].</param>
        /// <param name="nodeText">The node text.</param>
        /// <param name="nodeValue">The node value.</param>
        private void UpdateNode(CodeActivityContext context,
            XmlDocument doc,
            XmlNode parentNode, 
            bool enableSigning, 
            string nodeText,
            string nodeValue)
        {
            try
            {
                XmlElement newElement;
                XmlNode node1 = parentNode.SelectSingleNode(nodeText);

                if (node1 == null)
                {
                    if (enableSigning)
                    {
                        newElement = doc.CreateElement(nodeText, doc.DocumentElement.NamespaceURI);
                        newElement.InnerText = nodeValue;
                        parentNode.AppendChild(newElement);
                    }
                    else
                    {
                        node1.InnerText = nodeValue;
                    }
                }
                else
                {
                    if (enableSigning)
                    {
                        node1.InnerText = nodeValue;
                    }
                    else
                    {
                        parentNode.RemoveChild(node1);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.LogError(context, "Error updating Node: " + ex.Message);
            }
        }

        /// <summary>
        /// Changes the read only status.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        private void ChangeReadOnlyStatus(string filePath)
        {
            if ((File.GetAttributes(filePath) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                FileAttributes attribs = File.GetAttributes(filePath);
                attribs &= ~FileAttributes.ReadOnly;
                File.SetAttributes(filePath, attribs);
            }
        }
    }
}